package net.da.tools;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

public class JavaFileCreator {

	private static final String INTERFACE = "interface";

	private static final String CLASS = "class";

	private static final String ABSTRACT = "abstract";

	private static final String EXTENDS = "extends";

	private static final String IMPLEMENTS = "implements";

	private static final String PACKAGE = "package";

	private static final String IMPORT = "import";

	private StringBuffer buffer;

	private ClassDescriptor classDescriptor;

	private String folder;

	public void createJavaFile() {

		String packageName = classDescriptor.getPackageName();
		packageName = packageName.replace('.', '/');
		String folders = folder + '/' + packageName;
		File file = new File(folders);
		file.mkdirs();
		file = new File(folders + '/' + classDescriptor.getShortName()
				+ ".java");
		try {
			FileWriter fw = new FileWriter(file);
			assembleBody();
			fw.append(buffer);
			fw.close();
			file.createNewFile();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}

	}

	String assembleBody() {

		this.buffer = new StringBuffer();
		writePackageDeclaration();
		writeNewLine();
		writeImportsDeclaration();
		writeNewLine();
		writeClassDeclarartion();
		writeNewLine();
		writeNewLine();
		writeFileds();
		writeMethods();
		writeCloseBracket();
		writeNewLine();
		writeNewLine();
		return buffer.toString();
	}

	void writeCloseBracket() {
		buffer.append("}");
	}

	void writeOpenBracket() {
		buffer.append("{");
	}

	void writeMethods() {

		List<MethodDescriptor> methods = classDescriptor.getMethods();
		for (MethodDescriptor descriptor : methods) {
			writeMethod(descriptor);
			writeNewLine();
			writeNewLine();
		}
	}

	void writeFileds() {

		if (!classDescriptor.isHasFields()) {
			return;
		}
		List<FieldDescriptor> fields = classDescriptor.getFields();
		for (FieldDescriptor descriptor : fields) {
			writeSpace(4);
			writeField(descriptor);
			writeNewLine();
			writeNewLine();
		}
	}

	void writeSpace(StringBuffer buffer, int i) {
		for (int j = 0; j < i; j++) {
			buffer.append(' ');
		}
	}

	void writeSpace(int i) {
		writeSpace(buffer, i);
	}

	void writeField(FieldDescriptor field) {

		String accessQualifier = field.getAccessQualifier();
		String fieldName = field.getName();
		String fieldTypeName = field.getType().getName();
		writeString(accessQualifier);
		writeSpace(1);
		writeString(fieldTypeName);
		writeSpace(1);
		writeString(fieldName);
		writeSemicolon();
	}

	void writeClassDeclarartion() {

		String accessQuilifier = classDescriptor.getAccessQuilifier();
		writeString(accessQuilifier);
		if (!isEmpty(accessQuilifier)) {
			writeSpace(1);
		}

		if (classDescriptor.isAbstractClass()) {
			writeString(ABSTRACT);
			writeSpace(1);
		}
		String classType = classDescriptor.isInterfaceType() ? INTERFACE
				: CLASS;
		writeString(classType);
		writeSpace(1);
		String name = classDescriptor.getShortName();
		writeString(name);
		writeSpace(1);

		if (classDescriptor.isInterfaceType()) {
			if (classDescriptor.isHasInterfaces()) {
				writeString(EXTENDS);
				writeSpace(1);
			}
		} else {
			if (classDescriptor.isHasSuperType()) {
				ClassDescriptor superType = classDescriptor.getSuperType();
				String superTypeName = superType.getShortName();
				writeString(EXTENDS);
				writeSpace(1);
				writeString(superTypeName);
				writeSpace(1);
			}
			if (classDescriptor.isHasInterfaces()) {
				writeString(IMPLEMENTS);
				writeSpace(1);
			}
		}

		if (classDescriptor.isHasInterfaces()) {
			writeInterfaces(classDescriptor);
			writeSpace(1);
		}
		writeOpenBracket();
	}

	void writeInterfaces(ClassDescriptor classDescriptor) {

		List<ClassDescriptor> interfaces = classDescriptor.getInterfaces();
		for (Iterator<ClassDescriptor> iter = interfaces.iterator(); iter
				.hasNext();) {
			ClassDescriptor interfaze = iter.next();
			String name = interfaze.getShortName();
			writeString(name);
			if (iter.hasNext()) {
				writeComma();
				writeSpace(1);
			}
		}
	}

	void writeComma() {
		writeComma(buffer);
	}

	void writeComma(StringBuffer buffer) {
		buffer.append(',');
	}

	void writeMethod(MethodDescriptor method) {

		if (classDescriptor.isInterfaceType()) {
			writeIntrfaceMethod(method);
		} else if (method.isAbstractMethod()) {
			writeAbstractMethod(method);
		} else {
			writeBodyMethod(method);
		}
	}

	void writeAbstractMethod(MethodDescriptor method) {
		writeSpace(4);
		writeMethodHeader(method);
		writeSemicolon();
	}

	private void writeMethodHeader(MethodDescriptor method) {
		String header = buildMethodHeader(method);
		writeString(header);
	}

	void writeIntrfaceMethod(MethodDescriptor method) {
		writeAbstractMethod(method);
	}

	String buildMethodHeader(MethodDescriptor method) {

		StringBuffer buffer = new StringBuffer();
		boolean isAbstract = method.isAbstractMethod();

		// writeSpace(buffer, 4);
		String qualifier = method.getAccessQualifier();
		buffer.append(qualifier);
		writeSpace(buffer, 1);
		if (isAbstract) {
			buffer.append(ABSTRACT);
			writeSpace(buffer, 1);
		}

		if (!method.isConstructor()) {
			String retTypeName = method.getReturnType().getName();
			buffer.append(retTypeName);
			writeSpace(buffer, 1);
		}

		String name = method.getName();
		buffer.append(name);
		writeOpenCircleBracket(buffer);
		writeParams(buffer, method);
		writeCloseCircleBracket(buffer);
		return buffer.toString();
	}

	private void writeParams(StringBuffer buffer, MethodDescriptor method) {

		if (!method.isHasParams()) {
			return;
		}
		List<Parameter> params = method.getParams();
		for (Iterator<Parameter> iter = params.iterator(); iter.hasNext();) {
			Parameter param = iter.next();
			String paramName = param.getName();
			String typeName = param.getType().getName();
			buffer.append(typeName);
			writeSpace(buffer, 1);
			buffer.append(paramName);
			if (iter.hasNext()) {
				writeComma(buffer);
				writeSpace(buffer, 1);
			}
		}
	}

	void writeCloseCircleBracket(StringBuffer buffer) {
		buffer.append(')');
	}

	void writeCloseCircleBracket() {
		writeOpenCircleBracket(buffer);
	}

	void writeOpenCircleBracket() {
		writeOpenCircleBracket(buffer);
	}

	void writeOpenCircleBracket(StringBuffer buffer) {
		buffer.append('(');
	}

	void writeBodyMethod(MethodDescriptor method) {

		String header = buildMethodHeader(method);
		MethodBody methodBody = method.getBody();
		writeBodyBlock(methodBody, header, 4);
	}

	void writeBodyElement(BodyElement bodyElement, int offset) {

		if (bodyElement instanceof BodyBlock) {
			BodyBlock bodyBlock = (BodyBlock) bodyElement;
			writeBodyBlock(bodyBlock, bodyBlock.getHeader(), offset);
		} else if (bodyElement instanceof BodyLine) {
			BodyLine bodyLine = (BodyLine) bodyElement;
			writeBodyLine(bodyLine, offset);
		} else {
			throw new RuntimeException("Body elemtnt is incorrect!");
		}
	}

	void writeBodyBlock(BodyBlock block, String header, int offset) {
		
		if (block == null) {
			return;
		}
		List<BodyElement> elements = block.getElements();

		boolean isHasHeader = !isNullOrEmpty(header);
		if (isHasHeader) {
			writeSpace(offset);
			writeString(header);
			writeSpace(1);
		}
		writeOpenBracket();
		writeNewLine();
		for (BodyElement element : elements) {
			// if (isHasHeader) {
			// writeBodyElement(element, offset + 4);
			// } else {
			// writeBodyElement(element, offset + 4);
			// }
			writeBodyElement(element, offset + 4);
		}
		writeSpace(offset);
		writeCloseBracket();
		writeNewLine();
	}

	void writeBodyLine(BodyLine line, int offset) {
		writeSpace(offset);
		writeString(line.getLine());
		writeNewLine();
	}

	void writeImportsDeclaration() {

		List<ClassDescriptor> imports = extractClassesToImport();
		if (!imports.isEmpty()) {
			writeNewLine();
		}

		for (ClassDescriptor descriptor : imports) {
			String name = descriptor.getFullName();
			writeString(IMPORT);
			writeSpace(1);
			writeString(name);
			writeSemicolon();
			writeNewLine();
		}
	}

	private List<ClassDescriptor> extractClassesToImport() {
		
		Set<ClassDescriptor> all = extractAllUsedClasses();
		List<ClassDescriptor> required = new ArrayList<ClassDescriptor>();
		for (ClassDescriptor clazz : all) {			
			if (!clazz.getPackageName().startsWith("java.lang")){
				required.add(clazz);
			} 
		}		
		return required;
	}

	Set<ClassDescriptor> extractAllUsedClasses() {

		Set<ClassDescriptor> set = new HashSet<ClassDescriptor>();
		if (classDescriptor.isHasFields()) {
			List<FieldDescriptor> fields = classDescriptor.getFields();
			for (FieldDescriptor field : fields) {
				TypeDescriptor typeDescriptor = field.getType();
				if (!typeDescriptor.isPrimitive()) {
					ClassDescriptor clazz = typeDescriptor.getClassDescriptor();
					set.add(clazz);
				}
			}
		}

		if (classDescriptor.isHasMethods()) {
			List<MethodDescriptor> methods = classDescriptor.getMethods();
			for (MethodDescriptor method : methods) {
				List<ClassDescriptor> types = method.getUsedClasses();
				for (ClassDescriptor clazz : types) {
					set.add(clazz);
				}
				if (method.getBody() != null) {
					List<ClassDescriptor> bodyClasses = method.getBody()
							.getAllUsages();
					for (ClassDescriptor clazz : bodyClasses) {
						set.add(clazz);
					}
				}
			}
		}

		if (classDescriptor.isHasInterfaces()) {
			List<ClassDescriptor> interfaces = classDescriptor.getInterfaces();
			for (ClassDescriptor interfaze : interfaces) {
				set.add(interfaze);
			}
		}

		if (classDescriptor.isHasSuperType()) {
			ClassDescriptor superType = classDescriptor.getSuperType();
			set.add(superType);
		}

		return set;
	}

	void writeNewLine() {
		buffer.append('\n');
	}

	void writeCircleOpentBracket() {
		buffer.append('(');
	}

	void writeCircleCloseBracket() {
		buffer.append(')');
	}

	void writePackageDeclaration() {

		String packaze = classDescriptor.getPackageName();
		if (!isNullOrEmpty(packaze)) {
			writeString(PACKAGE);
			writeSpace(1);
			writeString(packaze);
			writeSemicolon();
		}
	}

	void writeString(String str) {
		buffer.append(str);
	}

	void writeSemicolon() {
		buffer.append(';');
	}

	boolean isNullOrEmpty(String str) {
		return str == null || str.length() <= 0;
	}

	boolean isEmpty(String str) {
		return str.length() <= 0;
	}

	public ClassDescriptor getClassDescriptor() {
		return classDescriptor;
	}

	public void setClassDescriptor(ClassDescriptor classDescriptor) {
		this.classDescriptor = classDescriptor;
	}

	public String getFolder() {
		return folder;
	}

	public void setFolder(String folder) {
		this.folder = folder;
	}

}
